#include "stdafx.h"
#include "CppUnitTest.h"
#include <vex/iterable/concept/range_concepts.h>
#include <vex/query/from.h>
#include <vex/query/where.h>
#include <vex/iterable/make_any_range.h>
#include <vex.tests/mstest/tests.range/tests.range.h>
#include <vex.tests/mstest/tests.range/range_archetype.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace tests { namespace query_where_from_vector {
    using namespace boost::assign; 

    TEST_CLASS(test_class) {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.query.where_from_vector")
        END_TEST_CLASS_ATTRIBUTE();

        VEX_TESTS_TRACKED_MEM_CHECK();

        typedef std::vector<int> container_t;
        typedef container_t::iterator iterator_t;
        typedef container_t::const_iterator const_iterator_t;

        container_t 
            m_container, 
            m_container_same_size_different_values, 
            m_container_empty, 
            m_container_with_one_element;
    public:
        
        TEST_METHOD_INITIALIZE(set_up)
        {
            container_t().swap(m_container);
            container_t().swap(m_container_same_size_different_values);
            container_t().swap(m_container_empty);
            container_t().swap(m_container_with_one_element);

            m_container += 1, 2, 3, 4, 5;
            m_container_same_size_different_values += 6, 7, 8, 9, 5;
            m_container_with_one_element += 1;

            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();

            container_t().swap(m_container);
            container_t().swap(m_container_same_size_different_values);
            container_t().swap(m_container_empty);
            container_t().swap(m_container_with_one_element);
        }

        //TEST_METHOD(supports_move_and_swap)
        //{
        //    auto t_r1 = vex::query::from(m_container)
        //        | vex::query::where([](int v) { return v % 2 == 0; });
        //    auto t_r2 = vex::query::from(m_container_same_size_different_values)
        //        | vex::query::where([](int v) { return v % 2 == 0; });

        //    bidirectional_range::has_expected_type_and_size<
        //        vi::stl_interop::range_from_iterator<iterator_t>
        //    >(t_r1, sizeof(vi::stl_interop::range_from_iterator<iterator_t>));

        //    bidirectional_range::supports_and_executes_swap_correctly(
        //        m_container, m_container_same_size_different_values, t_r1, t_r2);
        //}

        TEST_METHOD(supports_non_const_to_const_conversion_but_not_vice_versa)
        {
            typedef std::function<bool (int)> predicate_t;
            typedef decltype(vex::query::from(
                std::declval<iterator_t>(), std::declval<iterator_t>())
                | vex::query::where(std::declval<predicate_t>())) range_t;
            typedef decltype(vex::query::from(
                std::declval<const_iterator_t>(), std::declval<const_iterator_t>())
                | vex::query::where(std::declval<predicate_t>())) const_range_t;

            Assert::IsTrue(std::is_convertible<range_t, const_range_t>::value);
            Assert::IsFalse(std::is_convertible<const_range_t, range_t>::value);
        }

        TEST_METHOD(move_front_iteration_succeeds_expected_number_of_times)
        {            
            auto t_predicate = [](int v) { return v % 2 == 0; };
            bidirectional_range::move_front_iteration_succeeds_expected_number_of_times(
                vex::query::from(m_container_empty) | vex::query::where(t_predicate), 
                0);

            bidirectional_range::move_front_iteration_succeeds_expected_number_of_times(
                vex::query::from(m_container) 
                | vex::query::where(t_predicate), 2);

            bidirectional_range::move_front_iteration_succeeds_expected_number_of_times(
                vex::query::from(m_container.cbegin(), m_container.cend()) 
                | vex::query::where(t_predicate), 2);
        }

        TEST_METHOD(move_front_succeeds_n_times_on_range_with_n_elements)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            auto t_boost_filtered1 = 
                m_container | boost::adaptors::filtered(t_predicate);

            one_pass_range::move_front_succeeds_n_times_on_range_with_n_elements(
                vex::query::from(m_container) | vex::query::where(t_predicate), 
                t_boost_filtered1.begin(), t_boost_filtered1.end());

            auto t_boost_filtered2 = 
                m_container_same_size_different_values | boost::adaptors::filtered(t_predicate);

            one_pass_range::move_front_succeeds_n_times_on_range_with_n_elements(
                vex::query::from(m_container_same_size_different_values) 
                | vex::query::where(t_predicate), 
                t_boost_filtered2.begin(), t_boost_filtered2.end());
        }

        TEST_METHOD(after_calling_move_front_n_times_front_points_to_nth_element)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            container_t t_exp_range;
            boost::copy(m_container | boost::adaptors::filtered(t_predicate),
                std::back_inserter(t_exp_range));

            for (auto t_size = t_exp_range.size(); t_size > 0; --t_size) {
                one_pass_range::after_calling_move_front_n_times_front_points_to_nth_element(
                    vex::query::from(m_container) | vex::query::where(t_predicate), 
                    t_size, 
                    t_exp_range.begin(), t_exp_range.end());
            }
        }

        // forward iterable portion
        TEST_METHOD(save_creates_a_copy_of_range)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            forward_range::save_creates_a_copy_of_current_range(
                vex::query::from(m_container) | vex::query::where(t_predicate));
            forward_range::save_creates_a_copy_of_current_range(
                vex::query::from(m_container_with_one_element) | vex::query::where(t_predicate));
            forward_range::save_creates_a_copy_of_current_range(
                vex::query::from(m_container_empty) | vex::query::where(t_predicate));
        }

        // bidirectional_range portion
        TEST_METHOD(move_back_succeeds_n_times_on_range_with_n_elements)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            {
                container_t t_exp_range;
                boost::copy(m_container | boost::adaptors::filtered(t_predicate),
                    std::back_inserter(t_exp_range));

                bidirectional_range::move_back_succeeds_n_times_on_range_with_n_elements(
                    vex::query::from(m_container) | vex::query::where(t_predicate),
                    t_exp_range.crbegin(), t_exp_range.crend());
            }

            {
                container_t t_exp_range;
                boost::copy(m_container_empty | boost::adaptors::filtered(t_predicate),
                    std::back_inserter(t_exp_range));

                bidirectional_range::move_back_succeeds_n_times_on_range_with_n_elements(
                    vex::query::from(m_container_empty) | vex::query::where(t_predicate),
                    t_exp_range.crbegin(), t_exp_range.crend());
            }
        }

        TEST_METHOD(after_calling_move_back_n_times_back_points_to_nth_element_from_back)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            container_t t_exp_range;
            boost::copy(m_container | boost::adaptors::filtered(t_predicate),
                std::back_inserter(t_exp_range));

            for (auto t_size = t_exp_range.size(); t_size > 0; --t_size) {
                bidirectional_range::after_calling_move_back_n_times_back_points_to_nth_element_from_back(
                    vex::query::from(m_container) | vex::query::where(t_predicate), 
                    t_size, 
                    t_exp_range.crbegin(), t_exp_range.crend());
            }
        }

        TEST_METHOD(either_move_front_or_move_back_are_possible_on_range_with_one_element)
        {
            container_t t_input_range;
            t_input_range += 1, 2, 3;

            bidirectional_range::either_move_front_or_move_back_are_possible_on_range_with_one_element(
                vex::query::from(t_input_range) | vex::query::where([](int v) { return v % 2 == 0; }));
        }

        TEST_METHOD(front_and_back_are_equal_on_range_with_one_element)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            container_t t_input_range;
            t_input_range += 1, 2, 3;

            auto t_boost_filtered = t_input_range | boost::adaptors::filtered(t_predicate);
            Assert::AreEqual(t_boost_filtered.front(), t_boost_filtered.back());

            container_t t_exp_range;
            boost::copy(t_input_range | boost::adaptors::filtered(t_predicate),
                std::back_inserter(t_exp_range));

            bidirectional_range::front_and_back_are_equal_on_range_with_one_element(
                vex::query::from(t_input_range) | vex::query::where(t_predicate),
                t_exp_range.front());
        }

        TEST_METHOD(front_and_back_meet_in_middle_of_range_with_n_elements)
        {
            auto t_predicate = [](int v) { return v % 2 == 0; };

            auto t_exp_range = m_container | boost::adaptors::filtered(t_predicate);
            bidirectional_range::front_and_back_meet_in_middle_of_range_with_n_elements(
                vex::query::from(m_container) | vex::query::where(t_predicate),
                t_exp_range.begin(), 
                t_exp_range.end());
        }
    };
}}